﻿using System;
using System.Linq;
using System.IO;
using Heroius.Extension;
using EnvSafe.Expression;

namespace Heroius.BIU
{
    public class BIUEngine
    {
        public BIUEngine()
        {
            var factory = new FlexibleOperatorFactory();
            factory.Factorys.Add(new OperatorFactory());
            factory.Factorys.Add(new BIUOperatorFactory());
            explainer = new Interpreter(factory);
        }

        public BIUPlan Plan { get; set; }

        /// <summary>
        /// 表达式解析器
        /// </summary>
        Interpreter explainer;

        /// <summary>
        /// 合法的内置变量名
        /// </summary>
        static string[] GoodVar = { "name", "rela", "ssize", "dsize", "scdate", "dcdate", "swdate", "dwdate", "sadate", "dadate", "rdate", "rmsg", "ruser", "rresult", "rsrc", "iter" };

        public BIURecord Execute(BIURecord record)
        {
            //plan validation
            if (Plan == null)
            {
                return new BIURecord() { Result = BIURunResult.Failed, RunMessage = "执行计划为空", From = Plan.From };
            }
            // src & dst path check
            if (!Directory.Exists(Plan.From))
            {
                return new BIURecord() { Result = BIURunResult.Failed, RunMessage = "源文件夹不存在", From = Plan.From };
            }
            try
            {
                for (int i = 0; i < Plan.Rules.Count; i++) //遍历检验所有的规则
                {
                    var r = Plan.Rules[i];
                    //state-execution pair check
                    if ((r.State == FileState.OnlySrc && r.Execution == ExecutionOption.Delete))
                    {
                        return new BIURecord() { Result = BIURunResult.Failed, RunMessage = $"文件状态和执行冲突在第{i + 1}条规则中发现", From = Plan.From };
                    }
                    // filter variable name check & exp gen
                    if (string.IsNullOrWhiteSpace(r.FilterExp))
                    {
                        r.Filter = null;
                    }
                    else
                    {
                        r.Filter = explainer.GenerateProcedure(r.FilterExp);
                        foreach (var vn in r.Filter.GetUserVariables())
                        {
                            if ((!GoodVar.Contains(vn)) && !Plan.Variables.Any(t=>t.Value1 == vn))
                            {
                                return new BIURecord() { Result = BIURunResult.Failed, RunMessage = $"非法变量名 {vn} 在第{i + 1}条规则中发现", From = Plan.From };
                            }
                        }
                    }
                    // custom check
                    if (r.Execution == ExecutionOption.Custom)
                    {
                        if (string.IsNullOrWhiteSpace(r.CustomExp))
                        {
                            return new BIURecord() { Result = BIURunResult.Failed, RunMessage = $"自定义表达式为空，在第{i + 1}条规则", From = Plan.From };
                        }
                        else
                        {
                            r.Custom = explainer.GenerateProcedure(r.CustomExp);
                            foreach (var vn in r.Custom.GetUserVariables())
                            {
                                if ((!GoodVar.Contains(vn)) && !Plan.Variables.Any(t => t.Value1 == vn))
                                {
                                    return new BIURecord() { Result = BIURunResult.Failed, RunMessage = $"非法变量名 {vn} 在第{i + 1}条规则中发现", From = Plan.From };
                                }
                            }
                        }
                    }
                }

                DirectoryInfo src = new DirectoryInfo(Plan.From);
                DirectoryInfo dst = new DirectoryInfo(Plan.To);

                context = new BIUIterContext() { src = src, dst = dst, currentRela = "", rec = record };
                BIUOperatorFactory.Context = context;
                ExecuteInner(src.FullName, dst.FullName, "");

                return new BIURecord() { Result = BIURunResult.Succeed, RunMessage = "执行成功", From = Plan.From };
            }
            catch (Exception ex)
            {
                return new BIURecord() { Result = BIURunResult.Failed, RunMessage = ex.Message, From = Plan.From };
            }
        }

        /// <summary>
        /// 用于控制整个迭代过程的上下文，其内容更新在<see cref="ExecuteInner"/>中
        /// </summary>
        public BIUIterContext context { get; set; }
        
        void ExecuteInner(string currentsrc, string currentdst, string currentrela)
        {
            DirectoryInfo current_src = new DirectoryInfo(currentsrc);
            DirectoryInfo current_dst = new DirectoryInfo(currentdst);
            context.src = current_src;
            context.dst = current_dst;
            context.currentRela = $"{currentrela}";
            //文件
            foreach (var fs in current_src.GetFiles())
            {
                var fd = new FileInfo($"{current_dst.FullName}/{fs.Name}");
                //update inner iter context items
                context.fs = fs;
                context.fd = fd;

                foreach (var r in Plan.Rules)
                {
                    var ok = false;
                    switch (r.State)
                    {
                        case FileState.OnlySrc:
                            ok = !fd.Exists;
                            break;
                        case FileState.Both:
                            ok = fd.Exists;
                            break;
                    }
                    if (ok)
                    {
                        if (r.Filter != null)
                        {
                            TrySetVar(r.Filter, context);
                            ok = r.Filter.Calculate() == "1";
                        }
                        if (ok) //条件符合
                        {
                            context.iter++;
                            switch (r.Execution)
                            {
                                case ExecutionOption.Delete:
                                    {
                                        fd.Delete();
                                        RaiseNote($"Delete:{currentrela}/{fd.Name}");
                                    }
                                    break;
                                case ExecutionOption.Copy:
                                    {
                                        if (!current_dst.Exists)
                                        {
                                            current_dst.Create();
                                        }
                                        fs.CopyTo(fd.FullName, true);
                                        RaiseNote($"Copy:{currentrela}/{fs.Name}");
                                    }
                                    break;
                                case ExecutionOption.Custom:
                                    {
                                        TrySetVar(r.Custom, context);
                                        r.Custom.Calculate();
                                        RaiseNote($"Custom:{currentrela}/{fs.Name}");
                                    }
                                    break;
                            }
                            break; //一旦执行，则考察下一文件，避免多规则操作冲突。同时表明规则表的顺序敏感性，或关系，先满足先执行
                        }
                        //对于不满足过滤条件的直接略过
                    }
                    //对于不满足文件状态的直接略过
                }
            }
            //子目录
            foreach (var subs in current_src.GetDirectories())
            {
                var subd = $"{currentdst}/{subs.Name}";
                ExecuteInner(subs.FullName, subd, $"{currentrela}/{subs.Name}");
            }
        }

        public event EventHandler<BIUExecutionNotifyArgs> OnExecutionNotify;
        void RaiseNote(string logcontent)
        {
            OnExecutionNotify?.Invoke(this, new BIUExecutionNotifyArgs() { LogContent = logcontent });
        }

        /// <summary>
        /// 在迭代中设置表达式的
        /// </summary>
        /// <param name="proc"></param>
        /// <param name="context"></param>
        void TrySetVar(Procedure proc, BIUIterContext context)
        {
            var vs = proc.GetUserVariables();
            foreach (var v in vs)
            {
                switch (v)
                {
                    case "name":
                        proc.TrySetUserVariable(v, context.fs.Name);
                        break;
                    case "ssize":
                        proc.TrySetUserVariable(v, context.fs.Length.ToString());
                        break;
                    case "dsize":
                        proc.TrySetUserVariable(v, context.fd.Length.ToString());
                        break;
                    case "rela":
                        proc.TrySetUserVariable(v, context.currentRela);
                        break;
                    case "scdate":
                        proc.TrySetUserVariable(v, context.fs.CreationTime.ToOADate().ToString());
                        break;
                    case "dcdate":
                        proc.TrySetUserVariable(v, context.fd.CreationTime.ToOADate().ToString());
                        break;
                    case "swdate":
                        proc.TrySetUserVariable(v, context.fs.LastWriteTime.ToOADate().ToString());
                        break;
                    case "dwdate":
                        proc.TrySetUserVariable(v, context.fd.LastWriteTime.ToOADate().ToString());
                        break;
                    case "sadate":
                        proc.TrySetUserVariable(v, context.fs.LastAccessTime.ToOADate().ToString());
                        break;
                    case "dadate":
                        proc.TrySetUserVariable(v, context.fd.LastAccessTime.ToOADate().ToString());
                        break;
                    case "rdate":
                        proc.TrySetUserVariable(v, context.rec.BackupTime.ToOADate().ToString());
                        break;
                    case "rmsg":
                        proc.TrySetUserVariable(v, context.rec.RunMessage);
                        break;
                    case "ruser":
                        proc.TrySetUserVariable(v, context.rec.UserRemark);
                        break;
                    case "rresult":
                        proc.TrySetUserVariable(v, context.rec.Result.ToString());
                        break;
                    case "rsrc":
                        proc.TrySetUserVariable(v, context.rec.From);
                        break;
                    case "iter":
                        proc.TrySetUserVariable(v, context.iter.ToString());
                        break;
                    default:
                        proc.TrySetUserVariable(v, Plan.Variables.First(t => t.Value1 == v).Value2);
                        break;
                }
            }
        }
    }

    public class BIUExecutionNotifyArgs : EventArgs
    {
        public string LogContent { get; set; }
    }
}
